/*
 * Copyright 2009-2009 Tanesha Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 *    1. Redistributions of source code must retain the above copyright notice, 
 *    	 this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice,
 *    	 this list of conditions and the following disclaimer in the documentation 
 *    	 and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 *  OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are those 
 * of the authors and should not be interpreted as representing official policies, 
 * either expressed or implied, of the Tanesha Project.
 */
package com.google.api.chart;

import java.awt.Dimension;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import com.google.api.chart.encoding.DataEncoder;
import com.google.api.chart.encoding.ExtendedDataEncoder;
import com.google.api.chart.encoding.SimpleDataEncoder;
import com.google.api.chart.encoding.TextDataEncoder;

/**
 * ChartMaker is the main entrance for the ChartMaker API.
 * <p>
 * A chart can be specified using the ChartMaker api. There are 3 mandatory
 * parameters to produce a proper chart:
 * </p>
 * <ol>
 * 	 <li>Size of chart, use {@link ChartMaker#size(int, int)} or {@link ChartMaker#size(Dimension)} to specify size. There is a default size of 250x100.</li>
 *   <li>Data for chart, use the {@link ChartMaker#data(DataSeriesMaker...)} or {@link ChartMaker#data(double[][])} to specify data.</li>
 *   <li>Type of chart, use the {@link ChartMaker#type(ChartTypeMaker)} to set chart type.
 * </ol>
 * <p>
 * Once ChartMaker has been constructed and configured, there are two methods for getting the chart
 * {@link ChartMaker#getURL()} to get URL for chart, and {@link ChartMaker#getImageData()} to get
 * the chart image itself.
 * </p>
 * <p>
 * Encoding of chart data is default the "text" encoding, {@link ChartMaker#dataEncoder(DataEncoder)} and 
 * {@link ChartMaker#SIMPLE} or {@link ChartMaker#EXTENDED} can be used to specify alternative encoding.
 * </p>
 * 
 * @see SimpleChartMaker
 * @author sorenad
 *
 */
public class ChartMaker {

	/**
	 * A data encoder using "simple" schema
	 */
	public static DataEncoder SIMPLE = new SimpleDataEncoder();
	/**
	 * A data encoder using "extended" schema
	 */
	public static DataEncoder EXTENDED = new ExtendedDataEncoder();
	/**
	 * A data encoder using "text" schema
	 */
	public static DataEncoder TEXT = new TextDataEncoder();
	
	/**
	 * Creates a simple chart maker given a chart type.
	 * @param type The kind of chart to make
	 * @return
	 */
	public static SimpleChartMaker simple(ChartTypeMaker type) {
		return new SimpleChartMaker(type);
	}
	
	/**
	 * Creates a shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shape() {
		return ShapeMarkerMaker.shape();
	}

	/**
	 * Creates a specific shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shapeArrow() {
		return ShapeMarkerMaker.shape().arrow();
	}
	
	/**
	 * Creates a specific shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shapeCircle() {
		return ShapeMarkerMaker.shape().circle();
	}
	
	/**
	 * Creates a specific shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shapeDiamond() {
		return ShapeMarkerMaker.shape().diamond();
	}
	
	/**
	 * Creates a specific shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shapeCross() {
		return ShapeMarkerMaker.shape().cross();
	}

	/**
	 * Creates a specific shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shapeSquare() {
		return ShapeMarkerMaker.shape().square();
	}
	
	/**
	 * Creates a specific shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shapeXmark() {
		return ShapeMarkerMaker.shape().xmark();
	}

	/**
	 * Creates a specific shape marker
	 * @see ShapeMarkerMaker
	 * @return
	 */
	public static ShapeMarkerMaker shapeHorizontalLine(double position) {
		return ShapeMarkerMaker.shape().horizontalLine(position);
	}

	/**
	 * Creates a range marker
	 * @see RangeMarkerMaker
	 * @return
	 */
	public static RangeMarkerMaker range() {
		return RangeMarkerMaker.range();
	}

	/**
	 * Creates a financial marker
	 * @see FinancialMarkerMaker
	 * @return
	 */
	public static FinancialMarkerMaker financial() {
		return FinancialMarkerMaker.financial();
	}

	/**
	 * Creates a grid line
	 * @see GridLineMaker
	 * @return
	 */
	public static GridLineMaker gridline(double stepX, double stepY) {
		return GridLineMaker.gridline(stepX, stepY);
	}

	/**
	 * Creates a dataseries specified by an array of double values
	 * @see DataSeriesMaker
	 * @return
	 */
	public static DataSeriesMaker dataSeries(double... data) {
		return new DoublesDataSeriesMaker(data);
	}
	
	/**
	 * Creates a color specified by rgb values
	 * @see ColorMaker
	 * @return
	 */
	public static ColorMaker color(String rrggbb) {
		return ColorMaker.rrggbb(rrggbb);
	}
	
	/**
	 * Creates a dimension from two parameters
	 * @see FinancialMarkerMaker
	 * @return
	 */
	public static Dimension dim(int x, int y) {
		return new Dimension(x, y);
	}

	/**
	 * Creates a bar chart
	 * @see BarChartMaker
	 * @return
	 */
	public static BarChartMaker bar() {
		return new BarChartMaker();
	}

	/**
	 * Creates a radar chart
	 * @see RadarChartMaker
	 * @return
	 */
	public static RadarChartMaker radar() {
		return new RadarChartMaker();
	}

	/**
	 * Creates a line chart
	 * @see LineChartMaker
	 * @return
	 */
	public static LineChartMaker line() {
		return new LineChartMaker();
	}
	
	/**
	 * Creates a pie chart
	 * @see PieChartMaker
	 * @return
	 */
	public static PieChartMaker pie() {
		return new PieChartMaker();
	}
	
	/**
	 * Creates a venn diagram chart
	 * @see VennDiagramChartMaker
	 * @return
	 */
	public static VennDiagramChartMaker venn() {
		return new VennDiagramChartMaker();
	}
	
	/**
	 * Creates a scatter plot chart
	 * @see ScatterPlotChartMaker
	 * @return
	 */
	public static ScatterPlotChartMaker scatter() {
		return new ScatterPlotChartMaker();
	}

	/**
	 * Creates a line style.
	 * @see LineStyleMaker
	 * @return
	 */
	public static LineStyleMaker linestyle() {
		return LineStyleMaker.linestyle();
	}
	
	/**
	 * Creates a googleometer chart
	 * @see GoogleOMeterChartMaker
	 * @return
	 */
	public static GoogleOMeterChartMaker googleometer() {
		return GoogleOMeterChartMaker.googleometer();
	}
	
	/**
	 * Creates a scaling
	 * @see ScalingMaker
	 * @return
	 */
	public static ScalingMaker scaling(double minValue, double maxValue) {
		return ScalingMaker.scaling(minValue, maxValue);
	}

	/**
	 * Creates a data label
	 * @see DataLabelMaker
	 * @return
	 */
	public static DataLabelMaker datalabel() {
		return DataLabelMaker.datalabel();
	}
	
	/**
	 * Creates a number label for use by data labels
	 * @see NumberLabelMaker
	 * @see DataLabelMaker
	 * @return
	 */
	public static NumberLabelMaker numberlabel() {
		return NumberLabelMaker.numberlabel();
	}
	
	/**
	 * Creates chart legends
	 * @see ChartLegendMaker
	 * @return
	 */
	public static ChartLegendMaker legend(String... labels) {
		return ChartLegendMaker.legend(labels);
	}
	
	/**
	 * Creates axis
	 * @see AxisMaker
	 * @return
	 */
	public static AxisMaker axisBottomX() {
		return AxisMaker.bottomx();
	}
	/**
	 * Creates axis
	 * @see AxisMaker
	 * @return
	 */
	public static AxisMaker axisTopX() {
		return AxisMaker.topx();
	}
	/**
	 * Creates axis
	 * @see AxisMaker
	 * @return
	 */
	public static AxisMaker axisLeftY() {
		return AxisMaker.lefty();
	}
	/**
	 * Creates axis
	 * @see AxisMaker
	 * @return
	 */
	public static AxisMaker axisRightY() {
		return AxisMaker.righty();
	}
	/**
	 * Creates axis style
	 * @see AxisMaker
	 * @return
	 */
	public static AxisStyleMaker axisStyle() {
		return AxisStyleMaker.style();
	}
	/**
	 * Creates area fill with only underneath single dataset filling
	 * @see FillAreaMaker
	 * @return
	 */
	public static FillAreaMaker fillAreaUnder(ColorMaker color) {
		return FillAreaMaker.fillSingle(color);
	}
	/**
	 * Creates area fill
	 * @see FillAreaMaker
	 * @return
	 */
	public static FillAreaMaker fillArea() {
		return FillAreaMaker.fill();
	}
	/**
	 * Creates solid fill
	 * @see FillSolidMaker
	 * @return
	 */
	public static FillSolidMaker fillSolid() {
		return FillSolidMaker.fill();
	}
	/**
	 * Creates linear gradient fill
	 * @see LinearGradientFillMaker
	 * @return
	 */
	public static LinearGradientFillMaker fillLinearGradient() {
		return LinearGradientFillMaker.fill();
	}
	/**
	 * Creates linear stripes fill
	 * @see LinearStripesFillMaker
	 * @return
	 */
	public static LinearStripesFillMaker fillLinearStripes() {
		return LinearStripesFillMaker.fill();
	}

	/**
	 * Creates a margin
	 * @see MarginMaker
	 * @return
	 */
	public static MarginMaker margin() {
		return MarginMaker.margin();
	}

	/**
	 * Creates a margin
	 * @see MarginMaker
	 * @return
	 */
	public static MarginMaker margin(int left, int right, int top, int bottom) {
		return MarginMaker.margin().left(left).right(right).top(top).bottom(bottom);
	}

	private ChartTypeMaker type = bar();
	private Dimension size = new Dimension(250, 150);
	private List<DataSeriesMaker> dataSeries = new ArrayList<DataSeriesMaker>();
	private DataEncoder dataEncoder = new TextDataEncoder();
	
	public ChartMaker() {
	}

	/**
	 * Accessor for the image data at Google.
	 * @return
	 * @throws Exception
	 */
	public byte[] getImageData() throws Exception {
		return URLHelper.retrieveURL(getURL());
	}

	/**
	 * Generates the URL for the chart at Google.
	 * @return
	 */
	public String getURL() {
		return URLHelper.buildURL(type, size, dataEncoder, dataSeries);
	}

	/**
	 * Put data into the chart.
	 * @see ChartMaker#data(DataSeriesMaker...)
	 * @param data the data as doubles
	 * @return
	 */
	public ChartMaker data(double[][] data) {
		for (int i = 0; i < data.length; i++)
			dataSeries.add(new DoublesDataSeriesMaker(data[i]));
		return this;
	}

	/**
	 * Puts data into the chart.
	 * @param data A producer for the data
	 * @return
	 */
	public ChartMaker data(DataSeriesMaker... data) {
		for (int i = 0; i < data.length; i++)
			dataSeries.add(data[i]);
		return this;
	}

	/**
	 * Specify the size of the chart.  Default is 250x100
	 * @param size
	 * @return
	 */
	public ChartMaker size(Dimension size) {
		this.size = size;
		return this;
	}
	/**
	 * Specify the size of the chart image
	 * @param width
	 * @param height
	 * @return
	 */
	public ChartMaker size(int width, int height) {
		return size(dim(width, height));
	}

	/**
	 * Specify the type of the chart (including its layout in the type).
	 * @see ChartMaker#bar()
	 * @see ChartMaker#line()
	 * @see ChartMaker#scatter()
	 * @see ChartMaker#radar()
	 * @see ChartMaker#googleometer()
	 * @see ChartMaker#pie()
	 * @see ChartMaker#venn()
	 * @param type
	 * @return
	 */
	public ChartMaker type(ChartTypeMaker type) {
		this.type = type;
		return this;
	}
	
	/**
	 * Specify the data encoding type to use.
	 * 
	 * Default is to use TEXT encoding.
	 * 
	 * @see ChartMaker#TEXT
	 * @see ChartMaker#SIMPLE
	 * @see ChartMaker#EXTENDED
	 * @param dataEncoder
	 * @return
	 */
	public ChartMaker dataEncoder(DataEncoder dataEncoder) {
		this.dataEncoder  = dataEncoder;
		return this;
	}
	
	protected static class DoublesDataSeriesMaker implements DataSeriesMaker {
		private BigDecimal[] data;
		public DoublesDataSeriesMaker(double[] data) {
			this.data = new BigDecimal[data.length];
			for (int i = 0; i < data.length; i++)
				this.data[i] = BigDecimal.valueOf(data[i]);
		}
		@Override
		public BigDecimal[] getData() {
			return data;
		}
	}
}
